package org.vectrics.client.webclient;

import java.io.InputStream;
import java.util.Date;
import java.util.Properties;
import org.apache.log4j.Logger;
import org.vectrics.common.dom.DomUtils;
import org.vectrics.SystemException;
import org.vectrics.VectricsException;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.w3c.dom.Node;



abstract public class ServerCommandImpl implements ServerCommand {
	private transient Logger log = Logger.getLogger(this.getClass());
	private CommandResponseStatus responseStatus = CommandResponseStatus.NONE;
	private String responseInfo = "";
	abstract protected void processResponse(Element dataElement) throws VectricsException;
	private boolean forceAuthentication = false;
	private Document document = null;
	private String error = null;
	
	/**
	 * Constructor
	 */
	public ServerCommandImpl() {
		log.debug("make w3c document");
		try {
			document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
		}
		catch (Exception ex) {
			log.error("Exception creating document", ex);
		}
	}

	protected Element createElement(Element parentElement, String nodeName) {
		Element newElement = document.createElement(nodeName);
		parentElement.appendChild(newElement);
		return(newElement);
	}
	
	protected void handleError(Element errorElement) {
		String errorMessage = errorElement.getAttribute("message");
		log.warn("An error occurred while server was processing command:" + 
					errorMessage);
	}
	
	abstract protected void prepareRequestData(Element requestElement);

	public void forceAuthentication() {
		this.forceAuthentication = true;
	}


	/**
	 * 
	 */
	public final void getParameters(ServiceConnection serverConnection, Properties params) {
		Element parentElement = document.createElement("request");
		document.appendChild(parentElement);
		
		Element requestDataElement = document.createElement("data");
		if (document == null)
			throw new SystemException("getParameters() - null document");
		if (parentElement == null)
			throw new SystemException("getParameters() - null parentElement");
		parentElement.appendChild(requestDataElement);
		
		prepareRequestData(requestDataElement);		
		
		Date authExpDate = serverConnection.getAuthExpiration();
		Date now = new Date();
		if ((forceAuthentication) || (authExpDate == null) || (authExpDate.compareTo(now) >= 0)) {
			if (log.isDebugEnabled())
				log.debug("Sending user credentials, user = " + serverConnection.getUserName());
			if (serverConnection.getUserName() == null)
				throw new SystemException("Exception, request from server, but no user given for authentication");
			if (serverConnection.getPassword() == null)
				throw new SystemException("Exception, request from server, but no password given for authentication");
			Element authElement = document.createElement("auth"); 
			requestDataElement.appendChild(authElement);
			authElement.setAttribute("user", serverConnection.getUserName());
			authElement.setAttribute("password", serverConnection.getPassword());
			authElement.setAttribute("domainId", serverConnection.getDomainId());
		}
		params.setProperty("data", org.vectrics.common.dom.DomUtils.elementToXml(requestDataElement));			
	}




	public ResponseResult processResponse(ServiceConnection serverConnection, InputStream responseStream)
			 throws VectricsException {
		ResponseResult result = ResponseResult.OK;
		try {
			Document responseDocument = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(responseStream);
			Element rootElement = responseDocument.getDocumentElement();
			
			if (log.isDebugEnabled()) {
				log.debug("Response from server: " + DomUtils.elementToXml(rootElement));
			}
			
			NodeList errorNodes = rootElement.getElementsByTagName("error");
			for (int i = 0; i < errorNodes.getLength(); i++) {
				Element errorElement = (Element)errorNodes.item(i);
				log.debug("found error element");
				this.handleError(errorElement);
				String code = errorElement.getAttribute("code");
				if (code != null) {
					log.debug("Error had code attached");
					if (code.equals("no-auth")) {
						log.debug("No authentication on server, send server our credentials");
						result = ResponseResult.NEED_AUTH;
					}
					else if (code.equals("auth")) {
						log.info("Bad Authentication");
						result = ResponseResult.BAD_AUTH;
					}
					else {
						result = ResponseResult.NON_AUTH_ERROR;
					}
				}
				log.warn("Remote server reported error processing request: " +
						"\r\n Error" + result.getDescription());
			}
			
			if (result == ResponseResult.OK) {
				NodeList allNodes = rootElement.getChildNodes();
				
				for (int i = 0; i < allNodes.getLength(); i++) {
					Node node = allNodes.item(i);
					if (node.getNodeType() == Node.ELEMENT_NODE) {
						Element rootChild = (Element)node;
						if (rootChild.getNodeName().equals("data")) {
							log.debug("found data element");
							String sessionId = rootChild.getAttribute("session");
							log.debug("processResponse() - session = " + sessionId);
							if (sessionId == null) {
								log.error("Did not receive 'session' in data element from server");
								throw new VectricsException("Did not receive 'session' in data element from server");
							}
							serverConnection.setSessionId(sessionId);
							this.processResponse(rootChild);
						}
						else if (rootChild.getNodeName().equals("error")) {
							log.debug("found error element - already processed errors");
						}
						else {
							log.error("Do not know how to handle response element child: " + rootChild.getNodeName());
						}
					}
				}
			}
		}
		catch (Exception ex) {
			log.error("Exception getting response content", ex);
		}
		return(result);
	}
	
	
	public void connectionError(String info) {
		responseInfo = info;
		responseStatus = CommandResponseStatus.CONNECTION_ERROR;
	}
	

	public CommandResponseStatus getResponseStatus() {
		return responseStatus;
	}

	public String getResponseInfo() {
		return responseInfo;
	}

	public void setResponseInfo(String responseInfo) {
		this.responseInfo = responseInfo;
	}

	public String getError() {
		return error;
	}

	public void setError(String error) {
		log.warn("Connection Error - An error occurred processing a server command: " + error);
		this.error = error;
	}

}